package com.lj.common.utils;

import com.lj.common.exception.CommonException;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author luojing
 * @date 2025/3/23
 * 频率限制器
 */
public class FrequencyLimiter {

    /**
     * 时间窗口内最大调用次数
     */
    private final int maxCount;
    /**
     * 时间窗口
     */
    private final long windowMillis;

    /**
     * 计数器，用于计数当前时间窗口内调用的数量(可能会出现多线程的情况，所以使用原子类)
     */
    private final AtomicInteger counter = new AtomicInteger(0);
    /**
     * 最后一次重置的时间戳 (volatile保证可见性)
     */
    private volatile long lastResetTime = System.currentTimeMillis();
    /**
     * 重置的时候，为了保证counter和lastResetTime的原子性，需要进行加锁
     */
    private final Lock lock = new ReentrantLock();

    /**
     * @param maxCount   时间窗口内最大调用次数
     * @param timeWindow 时间窗口
     * @param unit       时间单位
     */
    public FrequencyLimiter(int maxCount, long timeWindow, TimeUnit unit) {
        this.maxCount = maxCount;
        this.windowMillis = unit.toMillis(timeWindow);
    }

    /**
     * 重置计数器与当前时间
     */
    public void reset() {
        counter.set(0);
        lastResetTime = System.currentTimeMillis();
    }

    /**
     * 检查是否本次调用达到了频率限制
     *
     * @return 达到了返回true 未达到返回false
     */
    public boolean check() {
        // 当前时间戳
        long currentTimeMillis = System.currentTimeMillis();
        // 距离上次触发的时间间隔
        long elapsed = currentTimeMillis - lastResetTime;

        // 超过了时间窗口，重置计数器与时间戳
        if (elapsed > windowMillis) {
            lock.lock();
            try {
                // 双重检查,防止其他线程重置
                // 这里为什么不用elapsed？因为可能其他线程已经重置了lastResetTime哦宝子！
                if (currentTimeMillis - lastResetTime > windowMillis) {
                    this.reset();
                }
            } finally {
                lock.unlock();
            }
        }
        // 检查计数器
        // 为什么先获取一遍? 临界时,如果已经超过了最大调用次数说明达到限制了就直接返回
        // counter是AtomicInteger类型的，如果不断自增会超过AtomicInteger的最大值就变成了负值，那么频率限制就失效了
        return counter.get() > maxCount || counter.incrementAndGet() > maxCount;
    }

    /**
     * 检查是否本次调用达到了频率限制
     * 达到了就抛出异常
     */
    public void checkIfThrow() {
        if (check()) {
            throw new CommonException("达到了频率限制!");
        }
    }
}
